/**
  * Copyright (C) 2010 IruVeli
  * http://iruveli.blogspot.com
  *
  * This file is part of DhiFix.
  * DhiFix is a program for creating dhivehi specific dictionary and
  * affix files for Hunspell.
  *
  * DhiFix is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gdk/gdkkeysyms.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#define RC_FILE "dhifix.rc"
#define TITLE "DhiFix"
#define DIC_FILE_NAME "dv_MV.dic"
#define AFF_FILE_NAME "dv_MV.aff"

enum {
    LIST_ITEM = 0,
    N_COLUMNS
};

/**
  * Represents a hunspell dictionary file.
  */
struct DIC_FILE {
    guint64 word_count; /** Approximate number of words in the dictionary */
    GHashTable *words;   /** HashTable of words in the dictionary. Key=String,Value=DIC_ENTRY*/
};

/**
  * Represents a hunspell dictionary file entry.
  */
struct DIC_ENTRY {
    GString *word;              /** Word */
    GSList *flags;              /** Affixes or special attributes, Type=DIC_FLAG */
    GString *optional_fields;   /** Optional fields */
};

/**
  * Represents a flag of an entry in a hunspell dictionary file.
  * Flags should be numeric and comma separated.
  * Also "FLAG num" option should be set in the respective hunspell affix file.
  */
struct DIC_FLAG {
    guint64 flag;   /** Flag as a number. Affix file must have flag type set to num. FLAG num */
    GSList *words;  /** The new words formed due to the application of the flag. Can be null if flag has no effect. */
};

/**
  * Represents a hunspell affix file.
  */
struct AFFIX_FILE {
    GSList *options;     /** Everything inside the affix file except affixes and comments that appear just before an affix. */
    GHashTable *affixes; /** All affix entries in the affixe file. */
};

/**
  * Represents an affix entry in a hunspell affix file.
  * Affix entry should have a numeric flag.
  * Hunspell affix file should have "FLAG num" option.
  */
struct AFFIX_ENTRY{
    GString *comments;      /** Comments that appear before the affix entry. */
    GString *option_name;   /** Type of affix. Either SFX or PFX. */
    guint64 flag;           /** Flag as number. Affix file must have flag type set to num. FLAG num */
    GString *cross_product; /** Permission to combine prefixes and suffixes. Y or N */
    GSList *rules;          /** List of rules under the affix flag. */
};

/**
  * Represents a rule under an affix entry in a hunspell affix file.
  */
struct AFFIX_RULE {
    GString *strip;             /** Stripping characters from the beginning (at prefix rules) or end (at suffix rules) of the word. */
    struct AFFIX *affix;               /** Affix (optionally with flags of continuation classes, separated by a slash. */
    GString *condition;         /** Condition to match before applying rule. */
    GString *optional_fields;   /** Optional morphological fields separated by spaces or tabulators. */
};

/**
  * Represents an affix and the subsequent flags that can be applied to
  * the word formed when the affix is applied to the stem word.
  */
struct AFFIX {
    GString *affix; /** Affix to the word. */
    GString *flags; /** Flags of continuations classes. */
};
 
void single_space(gchar *src);
gint load_dictionary_file(struct DIC_FILE **dictionary_file, GtkListStore *list);
gint load_affix_file(struct AFFIX_FILE **affix_file, gint *max_flag);
gint save_dictionary_file(struct DIC_FILE *dic_file);
void for_each_word_save_word(GString *key, struct DIC_ENTRY* dic_entry, GString* contents);
void for_each_word_flag_save_flag(struct DIC_FLAG* dic_flag, GString* contents);
gint save_affix_file(struct AFFIX_FILE* affix_file);
void for_each_option_save_option(GString* string, GString* contents);
void for_each_affix_entry_save_affix_entry(guint64 *flag, struct AFFIX_ENTRY* affix_entry, GString *contents);
void save();
void init_list(GtkWidget *list, GtkListStore *list_store);
void clear_list(GtkWidget *list);
void add_to_list(GtkWidget *list, const gchar *str);
void add_to_list_store(GtkListStore *list_store, const gchar *str);
void remove_selected_from_list(GtkWidget *list);
gint thaana_sort_func(gchar* a, gchar* b);
gint tree_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data);
gboolean word_search_equal_func(GtkTreeModel *model, gint column, const gchar* key, GtkTreeIter *iter, gpointer search_data);
int list_words_key_press(GtkWidget *list_words, GdkEventKey *event);
int list_affix_key_press(GtkWidget *list_words, GdkEventKey *event);
void add_new_word(GtkWidget *widget, GtkWidget *list_words);
void add_new_affixed_form(GtkWidget *widget, GtkWidget *list_affix);
struct AFFIX_ENTRY* find_affix(gchar* option_name, gchar* strip, gchar* affix, gchar* condition);
void suggest_new_affixed_forms(GtkWidget *widget, GtkWidget *list_affix);
gint compare_word_flag(const struct DIC_FLAG* a, const struct DIC_FLAG *b);
void on_changed(GtkWidget *selection, GtkWidget *list_affix);
void display_affixed_forms(GtkWidget *list_affix);
void apply_prefix(gchar* word, gchar* strip, gchar* affix, gchar* condition, gchar** result);
void apply_suffix(gchar* word, gchar* strip, gchar* affix, gchar* condition, gchar** result);

struct DIC_FILE *dic_file;
struct AFFIX_FILE *affix_file;
GString *current_word;
gint max_flag;
    
int main(int argc, char* argv[]) {

    GtkWidget *window;
    GtkWidget *table;
    GtkWidget *lblWord;
    GtkWidget *lblSuffix;
    GtkWidget *txtWord;
    GtkWidget *txtSuffix;
    GtkWidget *list_words;
    GtkWidget *list_affix;
    GtkWidget *btnAddWord;
    GtkWidget *btnAddAffix;
    GtkWidget *btnSave;
    GtkWidget *btnSuggest;
    GtkWidget *align;
    GtkWidget *scrolled;
    GtkWidget *spacer;
    GtkTreeSelection *selection;
    GtkListStore *list_store_words;
    GtkListStore *list_store_affix;
    clock_t start, end;
    double elapsed;
    
    start = clock();

    gtk_init(&argc, &argv);
    
    list_store_words = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
    list_store_affix = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
    
    /** Load the dictionary and affix files first. */
    if (load_affix_file(&affix_file, &max_flag) < 0) {
        printf("failed to load affix file.\n");
        return -1;
    }
    if (load_dictionary_file(&dic_file, list_store_words) < 0) {
        printf("failed to load dictionary file\n");
        return -1;
    }
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store_words), LIST_ITEM, tree_sort_func, NULL, NULL);
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(list_store_words),LIST_ITEM,GTK_SORT_ASCENDING);
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(list_store_affix),LIST_ITEM,GTK_SORT_ASCENDING);
    
    /** Initialise current_word */
    current_word = g_string_new("");
    
    /** Set up the UI. */
    gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
    
    gtk_rc_parse(RC_FILE);
    
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
    gtk_window_set_title(GTK_WINDOW(window), TITLE);
    
    gtk_container_set_border_width(GTK_CONTAINER(window), 20);
    
    table = gtk_table_new(5, 4, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(table), 10);
    gtk_table_set_col_spacings(GTK_TABLE(table), 10);
    
    btnSave = gtk_button_new_with_label("ސޭވްކުރޭ");
    gtk_table_attach(GTK_TABLE(table), btnSave, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
    
    btnSuggest = gtk_button_new_with_label("ޚިޔާލު");
    gtk_table_attach(GTK_TABLE(table), btnSuggest, 4, 5, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
    
    lblWord = gtk_label_new("ބަސް:");
    align = gtk_alignment_new(0, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(align), lblWord);
    gtk_table_attach(GTK_TABLE(table), align, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
    
    txtWord = gtk_entry_new();
    gtk_widget_set_size_request(txtWord, 200, 60);
    gtk_table_attach(GTK_TABLE(table), txtWord, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
    
    btnAddWord = gtk_button_new_with_label("އެއްކުރޭ");
    gtk_table_attach(GTK_TABLE(table), btnAddWord, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
    
    scrolled = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    
    list_words = gtk_tree_view_new();
    gtk_widget_set_size_request(list_words, 350, 400);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list_words), FALSE);
    gtk_tree_view_set_search_entry(GTK_TREE_VIEW(list_words), GTK_ENTRY(txtWord));
    init_list(list_words, list_store_words);
    gtk_container_add(GTK_CONTAINER(scrolled), list_words);
    gtk_table_attach(GTK_TABLE(table), scrolled, 0, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
    
    lblSuffix = gtk_label_new("އުފެދޭ ބަސް:");
    align = gtk_alignment_new(0, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(align), lblSuffix);
    gtk_table_attach(GTK_TABLE(table), align, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
    
    txtSuffix = gtk_entry_new();
    gtk_widget_set_size_request(txtSuffix, 200, 60);
    gtk_table_attach(GTK_TABLE(table), txtSuffix, 3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
    
    btnAddAffix = gtk_button_new_with_label("އެއްކުރޭ");
    gtk_table_attach(GTK_TABLE(table), btnAddAffix, 4, 5, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
    
    scrolled = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    
    list_affix = gtk_tree_view_new();
    gtk_widget_set_size_request(list_affix, 350, 400);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list_affix), FALSE);
    gtk_tree_view_set_search_entry(GTK_TREE_VIEW(list_affix), GTK_ENTRY(txtSuffix));
    init_list(list_affix, list_store_affix);
    gtk_container_add(GTK_CONTAINER(scrolled), list_affix);
    gtk_table_attach(GTK_TABLE(table), scrolled, 3, 5, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
    
    spacer = gtk_fixed_new();
    gtk_widget_set_size_request(spacer, 30,10);
    gtk_table_attach(GTK_TABLE(table), spacer, 2, 3, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
    
    gtk_container_add(GTK_CONTAINER(window), table);
    
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_words));
    
    g_signal_connect(btnSave, "clicked", G_CALLBACK(save), NULL);
    g_signal_connect(btnSuggest, "clicked", G_CALLBACK(suggest_new_affixed_forms), list_affix);
    g_signal_connect(btnAddWord, "clicked", G_CALLBACK(add_new_word), list_words);
    g_signal_connect(btnAddAffix, "clicked", G_CALLBACK(add_new_affixed_form), list_affix);
    g_signal_connect(selection, "changed", G_CALLBACK(on_changed), list_affix);
    g_signal_connect(list_words, "key-press-event", G_CALLBACK(list_words_key_press), NULL);
    g_signal_connect(list_affix, "key-press-event", G_CALLBACK(list_affix_key_press), NULL);
    g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), G_OBJECT(window));
    
    gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(list_words),word_search_equal_func, NULL, NULL);
    
    gtk_widget_show_all(window);
    
    end = clock();
    elapsed = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("Loading time:%f seconds\n", elapsed);
    
    gtk_main();
    
    return 0;
}

/**
  * Initializes a tree view widget as a list with the given GtkListStore.
  */
void init_list(GtkWidget *list, GtkListStore *list_store) {
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;
    GtkTreeSortable *sortable;
    
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("List Items", renderer, "text", LIST_ITEM, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
    
    //store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
    
    sortable = GTK_TREE_SORTABLE(list_store);
    //gtk_tree_sortable_set_sort_func(sortable, LIST_ITEM, tree_sort_func, NULL, NULL);
    //gtk_tree_sortable_set_sort_column_id(sortable, LIST_ITEM, GTK_SORT_ASCENDING);
    
    gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(list_store));
    
    g_object_unref(list_store);
    //gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),LIST_ITEM,GTK_SORT_ASCENDING);
}
/**
  * Clears all the items from a tree view widget.
  */
void clear_list(GtkWidget *list) {
    GtkListStore *store;
    
    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
    gtk_list_store_clear(store);    
}

/**
  * Adds a text item to a tree view widget.
  */
void add_to_list(GtkWidget *list, const gchar *str) {
    GtkListStore *store;
    GtkTreeIter iter;
    
    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
    
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);
}

/**
  * Adds a text item to a GtkListStore.
  */
void add_to_list_store(GtkListStore *list_store, const gchar *str) {
    GtkTreeIter iter;
    
    gtk_list_store_append(list_store, &iter);
    gtk_list_store_set(list_store, &iter, LIST_ITEM, str, -1);
}

/**
  * Removes the current selected item from a tree view widget.
  */
void remove_selected_from_list(GtkWidget *list) {
    GtkListStore *store;
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
    if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), &model, &iter)) {
        store = GTK_LIST_STORE(model);
        gtk_list_store_remove(store, &iter );
    }
}

/**
  * TODO:
  * Sort thaana words in terms of appearance of glyph and not unicode order which is
  * based on arabic.
  * ހށނރބޅކއވމފދތލގޏސޑޒޓޔޕޖޗޙޚޜޢޣޥޛޠޡޘޤޞޟޝ
  * އައާއިއީއުއޫއެއޭއޮއޯއް
  * -1 - a is less than b
  * 0  - both equal
  * 1  - a is greater than b
  */
gint thaana_sort_func(gchar* a, gchar* b) {
    return g_strcmp0(a, b);
}

/**
  * Tree sort function to replace the default function.
  * The default function is too slow for UTF-8.
  */
gint tree_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) {
    gchar *word_a;
    gchar *word_b;
    
    gtk_tree_model_get(model, a, LIST_ITEM, &word_a, -1);
    gtk_tree_model_get(model, b, LIST_ITEM, &word_b, -1);
    
    if ((word_a == NULL) || (word_b == NULL)) {
        if ((word_a == NULL) && (word_b == NULL)) {
            return 0;
        }
        return (word_a == NULL ? -1 : 1);
    } else {
        return g_strcmp0(word_a, word_b);
    }
}

/**
  * TODO:
  * Implement a more efficient search function that the default one.
  */
gboolean word_search_equal_func(GtkTreeModel *model, gint column, const gchar* key, GtkTreeIter *iter, gpointer search_data) {
    gchar *word;
    gint i;
    
    gtk_tree_model_get(model, iter, LIST_ITEM, &word, -1);
    
    if ((word == NULL) || (key == NULL)) return TRUE;
    if (strlen(word) < strlen(key)) return TRUE;
    
    for (i=0; i<strlen(key); i++) {
        if (word[i] != key[i]) return TRUE;
    }
    return FALSE;
}

/**
  * Deletes the selected word from the tree view (list_words) and
  * also removes it from the the list of words in the dictionary
  * when the delete key is pressed.
  */
int list_words_key_press(GtkWidget *list_words, GdkEventKey *event) {
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    gchar *word;
    
    if (event->keyval != GDK_Delete) return FALSE;
    
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_words));
    if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), &model, &iter)) {
        gtk_tree_model_get(model, &iter, LIST_ITEM, &word, -1);
        g_hash_table_remove(dic_file->words,word);
        remove_selected_from_list(list_words);
        return TRUE;
    }
    return FALSE;
}

/**
  * Removes the selected affixed form of the word from the list_affix and
  * also removes it from the flags attached to the word.
  */
int list_affix_key_press(GtkWidget *list_affix, GdkEventKey *event) {
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    gchar *affixed_word;
    GString *word;
    struct DIC_ENTRY *dic_entry;
    struct DIC_FLAG *dic_flag;
    gint i;
    gint j;
    GSList *list;
    
    if (event->keyval != GDK_Delete) return FALSE;
    
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_affix));
    if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), &model, &iter)) {
        gtk_tree_model_get(model, &iter, LIST_ITEM, &affixed_word, -1);
        dic_entry = (struct DIC_ENTRY*)g_hash_table_lookup(dic_file->words, current_word->str);
        if (dic_entry == NULL) return FALSE;
        for (i=0; i<g_slist_length(dic_entry->flags); i++) {
            list = g_slist_nth(dic_entry->flags, i);
            dic_flag = (struct DIC_FLAG*)list->data;
            for (j=0; j<g_slist_length(dic_flag->words); j++) {
                word = (GString*)g_slist_nth_data(dic_flag->words, j);
                if (g_strcmp0(word->str, affixed_word) == 0) {
                    dic_entry->flags = g_slist_delete_link(dic_entry->flags, list);
                    remove_selected_from_list(list_affix);
                    return TRUE;
                }
            }
        }
    }
    printf("list_affix_key_press - Error: cannot find affixed form in a ny flags for the word.\n");
    return FALSE;
}

/**
  * Display the affixed form of the word when it is selected in the tree view list_words.
  */ 
void on_changed(GtkWidget *selection, GtkWidget *list_affix)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    gchar *word;
    
    //printf("on_changed fired.\n");
    
    g_string_erase(current_word, 0, -1);
    clear_list(list_affix);
    
    if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), &model, &iter))
    {
        gtk_tree_model_get(model, &iter, LIST_ITEM, &word, -1);
        if ((word == NULL) || (strlen(word) < 1)) return;
        
        g_string_printf(current_word, "%s", word);
        display_affixed_forms(list_affix);
        
        g_free(word);
    }
}

/**
  * Display the affixed forms of the current_word in list_affix.
  * If the affixed form does not exist then the affixed form is constructed
  * using the flag.
  */
void display_affixed_forms(GtkWidget *list_affix) {
    gchar *new_word;
    GString *affixed_word;
    struct DIC_ENTRY *dic_entry;
    struct DIC_FLAG *dic_flag;
    struct AFFIX_ENTRY *affix_entry;
    struct AFFIX_RULE *affix_rule;
    struct AFFIX *affix;
    gint i;
    gint j;
    
    clear_list(list_affix);
    
    dic_entry = (struct DIC_ENTRY*)g_hash_table_lookup(dic_file->words, current_word->str);
    if (dic_entry->flags != NULL) {
        for (i=0; i<g_slist_length(dic_entry->flags); i++) {
            //printf("main do loop.\n");
            dic_flag = (struct DIC_FLAG*)g_slist_nth_data(dic_entry->flags, i);
            if (dic_flag->words != NULL) {
                //printf("affixed word already here.\n");
                for (j=0; j<g_slist_length(dic_flag->words); j++) {
                    affixed_word = (GString*)g_slist_nth_data(dic_flag->words, j);
                    //printf("affixed_word=%d\n",strlen(affixed_word->str));
                    add_to_list(list_affix, affixed_word->str);
                }
            } else {
                affix_entry = (struct AFFIX_ENTRY*)g_hash_table_lookup(affix_file->affixes, &dic_flag->flag);
                if ((affix_entry == NULL) || (affix_entry->rules == NULL))  continue;
                for (j=0; j<g_slist_length(affix_entry->rules); j++) {
                    affix_rule = (struct AFFIX_RULE*)g_slist_nth_data(affix_entry->rules, j);
                    affix = (struct AFFIX*)affix_rule->affix;
                    if (g_strcmp0(affix_entry->option_name->str, "PFX") == 0) {
                        apply_prefix(dic_entry->word->str, affix_rule->strip->str, affix->affix->str, affix_rule->condition->str, &new_word);
                        if (new_word != NULL) {
                            affixed_word = g_string_new(new_word);
                            dic_flag->words = g_slist_append(dic_flag->words, affixed_word);
                            add_to_list(list_affix, affixed_word->str);
                            g_free(new_word);
                        }
                    } else if (g_strcmp0(affix_entry->option_name->str, "SFX") == 0) {
                        apply_suffix(dic_entry->word->str, affix_rule->strip->str, affix->affix->str, affix_rule->condition->str, &new_word);
                        if (new_word != NULL) {
                            affixed_word = g_string_new(new_word);
                            dic_flag->words = g_slist_append(dic_flag->words, affixed_word);
                            //printf("affixed word=%s\n", affixed_word->str);
                            add_to_list(list_affix, affixed_word->str);
                            g_free(new_word);
                        }
                    }
                }
            }
        }
    }
}

/**
  * Returns the prefixed form of a word given the word, the part to strip,
  * affix to prefix and the condition to be satisfied.
  */
void apply_prefix(gchar* word, gchar* strip, gchar* affix, gchar* condition, gchar** result) {
    gchar* affixed_word;
    gint i;
    gint j;
    
    //printf("apply_prefix entered.\n");
    
    *result = NULL;
    
    if ((word == NULL) || (strip == NULL) || (affix == NULL) || (condition == NULL)) return;
    if ((strlen(strip) < 1) || (strlen(affix) < 1) || (strlen(condition) < 1)) return; 
    if (strlen(word) < strlen(strip)) return;
    /** only proceed if the conditions are satisfied. */
    /** note: we are not processing conditions which maybe expressed as a regular expression */
    if (!((g_str_has_prefix(word, condition)) || (g_strcmp0(condition, ".") == 0))) {
        return;
    }
    
    /** Allocate the maximum size the affixed word can be. */
    affixed_word = (gchar*)g_try_malloc0((strlen(word)+strlen(affix)+1)*sizeof(gchar));
    if (affixed_word == NULL) {
        printf("apply_prefix - Error allocating affixed_word.\n");
        return;
    }
    *result = affixed_word;
    
    /** Add the prefix first. */
    for (i=0; i<strlen(affix); i++) {
        affixed_word[i] = affix[i];
    }
    
    if (g_strcmp0(strip, "0") != 0) {
        /** If we are stripping then word should be longer than part to strip. */
        if (strlen(word) <= strlen(strip)) return;
        /** Check if the word contains the characters to be stripped. */
        if (!g_str_has_prefix(word, strip)) return;
        /** Also, gets the position of the first character in the word after the characters to be stripped. */
        j = strlen(strip);
    } else {
        j = 0;
    }
    
    /** Add the portion of the word remaining after stripping. */
    while (j < strlen(word)) {
        affixed_word[i] = word[j];
        i++;
        j++;
    }
    //printf("apply_prefix done.\n");
}

/**
  * Returns the suffixed form of a word given the word, the part to strip,
  * affix to prefix and the condition to be satisfied.
  */
void apply_suffix(gchar* word, gchar* strip, gchar* affix, gchar* condition, gchar** result) {
    gchar* affixed_word;
    gint affixed_word_length;
    gint i;
    gint j;
    
    //printf("apply_suffix entered.\n");
    
    *result = NULL;
    
    if ((word == NULL) || (strip == NULL) || (affix == NULL) || (condition == NULL)) return;
    if ((strlen(strip) < 1) || (strlen(affix) < 1) || (strlen(condition) < 1)) return; 
    if (strlen(word) < strlen(strip)) return;
    /** only proceed if the conditions are satisfied. */
    /** note: we are not processing conditions which maybe expressed as a regular expression */
    if (!((g_str_has_suffix(word, condition)) || (g_strcmp0(condition, ".") == 0))) {
        return;
    }
    
    /** Allocate the maximum size the affixed word can be. */
    affixed_word_length = (strlen(word)+strlen(affix));
    affixed_word = (gchar*)g_try_malloc0((affixed_word_length+1)*sizeof(gchar));
    if (affixed_word == NULL) {
        printf("apply_prefix - Error allocating affixed_word.\n");
        return;
    }
    *result = affixed_word;
        
    if (g_strcmp0(strip, "0") != 0) {
        /** If we are stripping then word should be longer than part to strip. */
        if (strlen(word) <= strlen(strip)) return;
        /** Check if the word contains the characters to be stripped. */
        if (!g_str_has_suffix(word, strip)) return;
        /** Copy the word till the portion we need to strip. */
        for (i=0; i<(strlen(word)-strlen(strip)); i++) {
            affixed_word[i] = word[i];
        }
    } else {
        /** Copy the whole word. */
        for (i=0; i<(strlen(word)); i++) {
            affixed_word[i] = word[i];
        }
    }
    
    /** Add the suffix. */
    for (j=0; j<strlen(affix); j++,i++) {
        affixed_word[i] = affix[j];
    }
    //printf("apply_suffix done.\n");
}

/**
  * Adds a new word to the dictionary and the tree view list_words.
  */
void add_new_word(GtkWidget *widget, GtkWidget *list_words) {
    GtkEntry *entry;
    struct DIC_ENTRY *dic_entry;
    gchar *word;
    
    entry = gtk_tree_view_get_search_entry(GTK_TREE_VIEW(list_words));
    
    word = g_strdup(gtk_entry_get_text(entry));
    
    dic_entry = (struct DIC_ENTRY*)g_try_malloc0(sizeof(struct DIC_ENTRY));
    if (dic_entry == NULL) {
        printf("add_new_word - Error allocating dic_entry.\n");
        return;
    }
    dic_entry->word = g_string_new(g_strstrip(word));
    g_hash_table_insert(dic_file->words, dic_entry->word->str, dic_entry);
        
    add_to_list(list_words, word);
    
    g_free(word);
}

/**
  * Adds a new affixed flag to the word. If the type of affix does not
  * exist then it is created.
  */
void add_new_affixed_form(GtkWidget *widget, GtkWidget *list_affix) {
    GtkEntry *entry;
    GString *new_word;
    GString *affixed_word;
    struct DIC_ENTRY *dic_entry;
    struct DIC_FLAG *dic_flag;
    struct AFFIX_ENTRY *affix_entry;
    struct AFFIX_RULE *affix_rule;
    struct AFFIX *s_affix;
    gchar *option_name;
    gchar *strip;
    gchar *condition;
    gchar *affix;
    gint i;
    gint j;
    
    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: entered.\n");
    
    entry = gtk_tree_view_get_search_entry(GTK_TREE_VIEW(list_affix));
    new_word = g_string_new(gtk_entry_get_text(entry));
    
    if (new_word->len < 2) return;
    if (g_strcmp0(current_word->str, new_word->str) == 0) {
        printf("add_new_affixed_form - Error affixed form is the same as the word.\n");
        return;
    }
    
    dic_entry = (struct DIC_ENTRY*)g_hash_table_lookup(dic_file->words, current_word->str);
    if (dic_entry == NULL) return;
    
    /** See if the affixed form already exists. */
    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: checking if affixed form exists.\n");
    for (i=0; i<g_slist_length(dic_entry->flags); i++) {
        dic_flag = (struct DIC_FLAG*)g_slist_nth_data(dic_entry->flags, i);
        g_assert(dic_flag != NULL);
        for (j=0; j<g_slist_length(dic_flag->words); j++) {
            affixed_word = (GString*)g_slist_nth_data(dic_flag->words, i);
            if (affixed_word == NULL) continue;
            if (g_strcmp0(new_word->str, affixed_word->str) == 0) {
                printf("add_new_affixed_form - Error affixed form already exists.\n");
                return;
            }
        }
    }
    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: affixed form does not exist.\n");
    
    /** Check if it is a prefix, suffix or neither*/
    /** If it is a prefix then last character should be the same. */
    /** If it is a suffix the first character should be the same. */
    /** Note: thaana required two bytes to be represented in UTF-8. */
    if ((new_word->str[0] == current_word->str[0]) && (new_word->str[1] == current_word->str[1])) {
        /** Suffix */
        g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: suggested form is suffixed.\n");
        option_name = g_strconcat("SFX", NULL);
        i = 0;
        while (current_word->str[i] == new_word->str[i]) i++;
        if (i == current_word->len) {
            strip = g_strconcat("0", NULL);
            condition = g_strconcat(".", NULL);
        } else {
            /** UTF-8 Dhivehi DE80 to DEBF */
            /** Any byte comparison would mean the first byte of the UTF8 character would match. */
            if (new_word->str[i] != (gchar)0xDE) i--;
            strip = (gchar*)g_try_malloc0((current_word->len-i+1)*sizeof(gchar));
            if (strip == NULL) {
                printf("add_new_affixed_form - Error allocating strip.\n");
                return;
            }
            for (j=i; j<current_word->len; j++) {
                strip[j-i] = current_word->str[j];
            }
            condition = g_strdup(strip);
        }
        affix = (gchar*)g_try_malloc0((new_word->len-i+1)*sizeof(gchar));
        if (affix == NULL) {
            printf("add_new_affixed_form - Error allocating affix.\n");
            return;
        }
        for (j=i; j<new_word->len; j++) {
            affix[j-i] = new_word->str[j];
        }
    } else if ((new_word->str[new_word->len-1] == current_word->str[current_word->len-1]) && 
                (new_word->str[new_word->len-2] == current_word->str[current_word->len-2])) {
        /** Prefix */
        g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: suggested form is prefixed.\n");
        option_name = g_strconcat("PFX", NULL);
        i = 0;
        while (current_word->str[current_word->len-i] == new_word->str[new_word->len-i]) i++;
        /** UTF8 problem will not occur since we are traversing backwards. */
        if (i == current_word->len) {
            strip = g_strconcat("0", NULL);
            condition = g_strconcat(".", NULL);
        } else {
            strip = (gchar*)g_try_malloc0((current_word->len-i+1)*sizeof(gchar));
            if (strip == NULL) {
                printf("add_new_affixed_form - Error allocating strip.\n");
                return;
            }
            for (j=i; j<=current_word->len; j++) {
                strip[j-i] = current_word->str[j-i];
            }
            condition = g_strdup(strip);
        }
        affix = (gchar*)g_try_malloc0((new_word->len-i+1)*sizeof(gchar));
        if (affix == NULL) {
            printf("add_new_affixed_form - Error allocating affix.\n");
            return;
        }
        for (j=i; j<new_word->len; j++) {
            affix[j-i] = new_word->str[j-i];
        }
    } else {
        /** Inavlid */
        printf("add_new_affixed_form - Error invalid affixed form suggested by user.\n");
        return;
    }
    //printf("finding affix.\n");
    affix_entry = find_affix(option_name, strip, affix, condition);
    //printf("finding affix complete.\n");
    if (affix_entry != NULL) {
        printf("matching affix found.\n");
        return;
    } else {
        max_flag++;
        affix_entry = (struct AFFIX_ENTRY*)g_try_malloc0(sizeof(struct AFFIX_ENTRY));
        if (affix_entry == NULL) {
            printf("add_new_affixed_form - Error allocating affix_entry.\n");
            return;
        }
        affix_entry->option_name = g_string_new(option_name);
        affix_entry->flag = max_flag;
        affix_entry->cross_product = g_string_new("Y");
        affix_rule = (struct AFFIX_RULE*)g_try_malloc0(sizeof(struct AFFIX_RULE));
        if (affix_rule == NULL) {
            printf("add_new_affixed_form - Error allocating affix_rule.\n");
            return;
        }
        affix_entry->rules = g_slist_append(affix_entry->rules, affix_rule);
        
        s_affix = (struct AFFIX*)g_try_malloc0(sizeof(struct AFFIX));
        if (s_affix == NULL) {
            printf("add_new_affixed_form - Error allocating affix.\n");
            return;
        }
        affix_rule->strip = g_string_new(strip);
        affix_rule->condition = g_string_new(condition);
        affix_rule->affix = s_affix;
        s_affix->affix = g_string_new(affix);
        g_hash_table_insert(affix_file->affixes, &affix_entry->flag, affix_entry);
    }
    //printf("adding affix.\n");
    dic_flag = (struct DIC_FLAG*)g_try_malloc0(sizeof(struct DIC_FLAG));
    if (dic_flag == NULL) {
        printf("add_new_affixed_form - Error allocating dic_flag.\n");
        return;
    }
    dic_flag->flag = affix_entry->flag;
    dic_flag->words = g_slist_append(dic_flag->words, new_word);
    dic_entry->flags = g_slist_append(dic_entry->flags, dic_flag);
    //add_to_list(list_affix, new_word->str);
    display_affixed_forms(list_affix);
    
    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "add_new_affixed_form: completed successfully.\n");
}

/**
  * Find a matching affix entry.
  */
struct AFFIX_ENTRY* find_affix(gchar* option_name, gchar* strip, gchar* affix, gchar* condition) {
    GList *list;
    struct AFFIX_ENTRY *affix_entry;
    struct AFFIX_RULE *affix_rule;
    struct AFFIX *s_affix;
    gint i;
    
    list = g_hash_table_get_keys(affix_file->affixes);
    
    if ((list == NULL) || (list->data == NULL)) return NULL;
    do {
        affix_entry = (struct AFFIX_ENTRY*)g_hash_table_lookup(affix_file->affixes, list->data);
        //printf("find_affix - flag=%d.\n", (int)affix_entry->flag);
        if (g_strcmp0(affix_entry->option_name->str, option_name) != 0) continue;
        for (i=0; i<g_slist_length(affix_entry->rules); i++) {
            affix_rule = (struct AFFIX_RULE*)g_slist_nth_data(affix_entry->rules, i);
            if (affix_rule == NULL) continue;
            s_affix = affix_rule->affix;
            g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "find_affix: %d %d %d vs. %d %d %d.\n",
                affix_rule->strip->len, affix_rule->condition->len, s_affix->affix->len, strlen(strip), strlen(condition), strlen(affix));
            if ((g_strcmp0(affix_rule->strip->str, strip) == 0) &&
                (g_strcmp0(affix_rule->condition->str, condition) == 0) &&
                (g_strcmp0(s_affix->affix->str, affix) == 0)) {
                return affix_entry;
            }
        }           
    } while ((list = list->next) != NULL);
    
    return NULL;
}

/**
  * Suggests new affixed forms for the current word and adds them to the list.
  * Note: does not handle regular expressions.
  * TODO: Add method to check for duplicate flags in dic_entry->flags
  */
void suggest_new_affixed_forms(GtkWidget *widget, GtkWidget *list_affix) {
    struct DIC_ENTRY *dic_entry;
    struct DIC_FLAG *dic_flag;
    struct AFFIX_ENTRY *affix_entry;
    struct AFFIX_RULE *affix_rule;
    GList *list;
    gint i;
    gint j;
    
    dic_entry = (struct DIC_ENTRY*)g_hash_table_lookup(dic_file->words, current_word->str);
    if (dic_entry == NULL) {
        printf("suggest_new_affixed_forms - Error: current_word does not exist.\n");
        return;
    }
    
    list = g_hash_table_get_values(affix_file->affixes);
    if (list == NULL) return;
    
    for (i=0; i<g_list_length(list); i++) {
        affix_entry = (struct AFFIX_ENTRY*)g_list_nth_data(list, i);
        for (j=0; j<g_slist_length(affix_entry->rules); j++) {
            affix_rule = (struct AFFIX_RULE*)g_slist_nth_data(affix_entry->rules, j);
            if ((g_strcmp0(affix_entry->option_name->str, "SFX") == 0) && 
                (g_str_has_suffix(current_word->str, affix_rule->strip->str)) &&
                (g_str_has_suffix(current_word->str, affix_rule->condition->str))) {
                dic_flag = (struct DIC_FLAG*)g_try_malloc0(sizeof(struct DIC_FLAG));
                if (dic_flag == NULL) {
                    printf("suggest_new_affixed_forms - Error allocating dic_flag.\n");
                    return;
                }
                dic_flag->flag = affix_entry->flag;
                if (g_slist_find_custom(dic_entry->flags, dic_flag, (GCompareFunc)compare_word_flag) == NULL) {
                    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "suggest_new_affixed_forms: adding flag %d.\n", (int)dic_flag->flag);
                    dic_entry->flags = g_slist_append(dic_entry->flags, dic_flag);
                }
            } else if ((g_strcmp0(affix_entry->option_name->str, "PFX") == 0) &&
                (g_str_has_prefix(current_word->str, affix_rule->strip->str)) &&
                (g_str_has_prefix(current_word->str, affix_rule->condition->str))) {
                dic_flag = (struct DIC_FLAG*)g_try_malloc0(sizeof(struct DIC_FLAG));
                if (dic_flag == NULL) {
                    printf("suggest_new_affixed_forms - Error allocating dic_flag.\n");
                    return;
                }
                dic_flag->flag = affix_entry->flag;
                if (g_slist_find_custom(dic_entry->flags, dic_flag, (GCompareFunc)compare_word_flag) == NULL) {
                    g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "suggest_new_affixed_forms: adding flag %d.\n", (int)dic_flag->flag);
                    dic_entry->flags = g_slist_append(dic_entry->flags, dic_flag);
                }
            }
        }
    }
    display_affixed_forms(list_affix);
}

/**
  * Compares DIC_FLAG.
  */
gint compare_word_flag(const struct DIC_FLAG* a, const struct DIC_FLAG *b) {
    return (a->flag == b->flag ? 0 : (a->flag < b->flag ? -1 : 1));
}

/**
  * Converts all mutiple spaces into single spaces.
  */
void single_space(gchar *src) {
    gchar *dst = src;
    
    for (; *src; ++src) {
        *dst++ = *src;
        if (g_ascii_isspace(*src)) {
            do ++src; while (g_ascii_isspace(*src));
            --src;
        }
    }
    *dst = 0;
}

/**
  * Loads an affix file.
  * If you want to modify something then pass a pointer to that.
  * If you want to modify a pointer then pass a pointer to the pointer.
  */
gint load_affix_file(struct AFFIX_FILE **aff_file, gint *max_flag) {
    gchar *contents;
    gchar **lines;
    gchar **affix_line;
    gchar **affix_options;
    GError *error;
    int i;
    int j;
    guint64 flag;
    struct AFFIX_FILE *affix_file;
    struct AFFIX_ENTRY *affix_entry;
    struct AFFIX_RULE *affix_rule;
    struct AFFIX *affix;
    GString *comments;
    
    if (!g_file_get_contents(AFF_FILE_NAME, &contents, NULL, &error)) {
        printf("load_aff - Error %d: %s\n", error->code, error->message); 
        return -1;
    }
    
    affix_file = (struct AFFIX_FILE*)g_try_malloc0(sizeof(struct AFFIX_FILE));
    if (affix_file == NULL) {
        printf("load_aff - Error allocating affix_file\n");
        return -1;
    }
    *aff_file = affix_file;
    affix_file->affixes = g_hash_table_new(g_str_hash, g_str_equal);
    affix_file->options = NULL;
    
    comments = NULL;
    *max_flag = 0;
    
    lines = g_strsplit(contents, "\n", -1);
    for (i=0; i<g_strv_length(lines); i++) {
        if ((g_str_has_prefix(lines[i], "PFX")) || (g_str_has_prefix(lines[i], "SFX"))) {
            single_space(lines[i]);
            affix_line = g_strsplit(lines[i], " ", -1);
            if (g_strv_length(affix_line) > 4) {
                flag = g_ascii_strtoull(affix_line[1], NULL, 0);
                affix_entry = (struct AFFIX_ENTRY*)g_hash_table_lookup(affix_file->affixes, &flag);
                if (affix_entry == NULL) {
                    printf("load_aff - Affix rules without header in affix file line %d\n", i);
                    return -1;
                }
                affix_rule = (struct AFFIX_RULE*)g_try_malloc0(sizeof(struct AFFIX_RULE));
                if (affix_rule == NULL) {
                    printf("load_aff - Error allocating affix_rule.\n");
                    return -1;
                }
                affix_rule->strip = g_string_new(affix_line[2]);
                affix_rule->condition = g_string_new(affix_line[4]);
                if (affix_line[5] != NULL) {
                    affix_rule->optional_fields = g_string_new(affix_line[5]);
                    j = 6;
                    while (affix_line[j] != NULL) {
                        g_string_append_printf(affix_rule->optional_fields, " %s", affix_line[j]);
                        j++;
                    }
                }
                affix = (struct AFFIX*)g_try_malloc0(sizeof(struct AFFIX));
                if (affix == NULL) {
                    printf("load_aff - Error allocating affix.\n");
                    return -1;
                }
                affix_options = g_strsplit(affix_line[3], "/", -1);
                affix->affix = g_string_new(affix_options[0]);
                if (g_strv_length(affix_options) > 1) {
                    affix->flags = g_string_new(affix_options[1]);
                }
                affix_rule->affix = affix;
                affix_entry->rules = g_slist_append(affix_entry->rules, affix_rule);
                
                g_strfreev(affix_options);
            } else if (g_strv_length(affix_line) > 3) {
                flag = g_ascii_strtoull(affix_line[1], NULL, 0);
                affix_entry = (struct AFFIX_ENTRY*)g_hash_table_lookup(affix_file->affixes, &flag);
                if (affix_entry != NULL) {
                    printf("load_aff - Duplicate definition of affix rule in line %d of affix file.\n", i);
                    return -1;
                }
                if (*max_flag < flag) {
                    *max_flag = flag;
                }
                
                affix_entry = (struct AFFIX_ENTRY*)g_try_malloc0(sizeof(struct AFFIX_ENTRY));
                if (comments != NULL) {
                    affix_entry->comments = comments;
                    comments = NULL;
                }
                affix_entry->option_name = g_string_new(affix_line[0]);
                affix_entry->flag = flag;
                affix_entry->cross_product = g_string_new(affix_line[2]);
                g_hash_table_insert(affix_file->affixes, &affix_entry->flag, affix_entry);
            } else {
                printf("load_aff - incomplete affix in line %d in affix file.\n", i);
                return -1;
            }
            g_strfreev(affix_line);
        } else if (g_str_has_prefix(lines[i], "#")) {
            if (comments == NULL) {
                comments = g_string_new(lines[i]);
            } else {
                g_string_append_printf(comments, "\n%s", lines[i]);
            }
        } else {
            if (strlen(g_strstrip(lines[i])) > 0) {
                if (comments != NULL) {
                    g_string_append_printf(comments, "\n%s", lines[i]);
                    affix_file->options = g_slist_append(affix_file->options, comments);
                    comments = NULL;
                } else {
                    affix_file->options = g_slist_append(affix_file->options, g_string_new(lines[i]));
                }
            }
        }
    }
    
    g_strfreev(lines);
    g_free(contents);
    
    return 0;
}
/**
  * Loads a dictionary file.
  */
gint load_dictionary_file(struct DIC_FILE **dictionary_file, GtkListStore *list) {
    gchar *contents;
    gchar **lines;
    gchar **line_split;
    gchar **option_split;
    gchar **flag_split;
    GError *error;
    gint line_count;
    gint i;
    gint j;
    struct DIC_FILE *dic_file;
    struct DIC_ENTRY *dic_entry;
    struct DIC_FLAG *dic_flag;
    
    if (!g_file_get_contents(DIC_FILE_NAME, &contents, NULL, &error)) {
        printf("load_dic - Error %d: %s\n", error->code, error->message);
        return -1;
    }
    
    dic_file = (struct DIC_FILE*)g_try_malloc0(sizeof(struct DIC_FILE));
    if (dic_file == NULL) {
        printf("load_dic - Error allocating space for dic_file\n");
        return -1;
    }
    *dictionary_file = dic_file;
    
    lines = g_strsplit(contents, "\n", -1);
    if ((line_count = g_strv_length(lines)) < 2) {
        printf("load_dic - Dictionary does not contain any words.\n");
        return -1;
    }
    dic_file->word_count = g_ascii_strtoull(lines[0], NULL, 0);
    dic_file->words = g_hash_table_new(g_str_hash, g_str_equal);
    
    for (i=1; i<line_count; i++) {
        line_split = g_strsplit(lines[i], "/", -1);
        dic_entry = (struct DIC_ENTRY*)g_try_malloc0(sizeof(struct DIC_ENTRY));
        if (dic_entry == NULL) {
            printf("load_dic - Error allocating dic_entry\n");
            return -1;
        }
        dic_entry->word = g_string_new(g_strstrip(line_split[0]));
        if (g_hash_table_lookup(dic_file->words, dic_entry->word->str) != NULL) printf("duplicate %d\n", i);
        g_hash_table_insert(dic_file->words, dic_entry->word->str, dic_entry);
        add_to_list_store(list, dic_entry->word->str);
        
        if (line_split[1] != NULL) {
            single_space(line_split[1]);
            option_split = g_strsplit(line_split[1], " ", -1);
            flag_split = g_strsplit(option_split[0], ",", -1);
            for (j=0; j<g_strv_length(flag_split); j++) {
                dic_flag = (struct DIC_FLAG*)g_try_malloc0(sizeof(struct DIC_FLAG));
                if (dic_flag == NULL) {
                    printf("load_dic - Error allocating dic_flag\n");
                    return -1;
                }
                dic_flag->flag = g_ascii_strtoull(flag_split[j], NULL, 0);
                dic_entry->flags = g_slist_append(dic_entry->flags, dic_flag);
            }
            if (g_strv_length(option_split) > 1) {
                dic_entry->optional_fields = g_string_new(option_split[1]);
            }
            g_strfreev(option_split);
            g_strfreev(flag_split);
        }
        g_strfreev(line_split);
    }
    
    g_strfreev(lines);
    g_free(contents);
    
    return 0;
}

/**
  * Saves affix file.
  */
gint save_affix_file(struct AFFIX_FILE* affix_file) {
    GString *contents;
    //GString *options;
    GError *error;
    
    if (affix_file == NULL) {
        printf("save_affix_file - Error affix_file is NULL.\n");
        return -1;
    }
    
    contents = g_string_new("");
    
    g_slist_foreach(affix_file->options, (GFunc)for_each_option_save_option, contents);
    g_hash_table_foreach(affix_file->affixes, (GHFunc)for_each_affix_entry_save_affix_entry, contents);
    
    error = NULL;
    if (!g_file_set_contents(AFF_FILE_NAME, contents->str, -1, &error)) {
        printf("save_affix_file - Error %d: %s\n", error->code, error->message);
        return -1;
    }
    g_string_free(contents, TRUE);
    
    printf("Affix file saved successfully.\n");
    return 0;
}

/**
  * Save all lines in the affix file except affixes.
  */
void for_each_option_save_option(GString* string, GString* contents) {
    if ((string == NULL) || (contents == NULL)) return;
    
    g_string_append_printf(contents, "%s\n\n", string->str);
}

/**
  * Save all affixes.
  */
void for_each_affix_entry_save_affix_entry(guint64 *flag, struct AFFIX_ENTRY* affix_entry, GString *contents) {
    struct AFFIX_RULE *affix_rule;
    struct AFFIX *affix;
    gint i;
    
    if ((affix_entry == NULL) || (contents == NULL)) return;
    if (g_slist_length(affix_entry->rules) < 1) return; /** For it to be valid there should be at least one rule. */
    
    if (affix_entry->comments != NULL) {
        g_string_append_printf(contents, "%s\n", affix_entry->comments->str);
    }
    g_string_append_printf(contents, "%s %d %s %d\n",
        affix_entry->option_name->str, (int)affix_entry->flag, affix_entry->cross_product->str, g_slist_length(affix_entry->rules));
    
    for (i=0; i<g_slist_length(affix_entry->rules); i++) {
        affix_rule = (struct AFFIX_RULE*)g_slist_nth_data(affix_entry->rules, i);
        affix = affix_rule->affix;
        g_string_append_printf(contents, "%s %d %s %s", affix_entry->option_name->str, (int)affix_entry->flag, affix_rule->strip->str, affix->affix->str);
        if (affix->flags != NULL) {
            g_string_append_printf(contents, "/%s", affix->flags->str); 
        }
        g_string_append_printf(contents, " %s", affix_rule->condition->str);
        if (affix_rule->optional_fields != NULL) {
            g_string_append_printf(contents, " %s", affix_rule->optional_fields->str);
        }
        g_string_append_printf(contents, "\n");
    }
    g_string_append_printf(contents, "\n");
}

/**
  * Saves the dictionary to file.
  */
gint save_dictionary_file(struct DIC_FILE *dic_file) {
    GString *contents;
    GError *error;
    GList *list;
    struct DIC_ENTRY* dic_entry;
    
    if (dic_file == NULL) {
        printf("save_dictionary_file - Error dic_file is NULL.\n");
        return -1;
    }
    
    contents = g_string_new("");
    g_string_append_printf(contents, "%d", g_hash_table_size(dic_file->words));
    
    list = g_hash_table_get_keys(dic_file->words);
    if (list == NULL) return -1;
    list = g_list_sort(list, (GCompareFunc)thaana_sort_func);
    
    do {
        dic_entry = (struct DIC_ENTRY*)g_hash_table_lookup(dic_file->words, list->data);
        if (dic_entry == NULL) continue;
        
        g_string_append_printf(contents, "\n%s", dic_entry->word->str);
        if (dic_entry->flags != NULL) {
            g_string_append_printf(contents, "/");
            g_slist_foreach(dic_entry->flags, (GFunc)for_each_word_flag_save_flag, contents);
        }
        if (dic_entry->optional_fields != NULL) {
            g_string_append_printf(contents, " %s", dic_entry->optional_fields->str);
        }
    } while ((list = list->next) != NULL);
    
    //g_hash_table_foreach(dic_file->words, (GHFunc)for_each_word_save_word, contents);
    
    error = NULL;
    if (!g_file_set_contents(DIC_FILE_NAME, contents->str, -1, &error)) {
        printf("save_dictionary_file - Error %d: %s\n", error->code, error->message);
        return -1;
    }
    
    g_string_free(contents, TRUE);
    
    printf("Dictionary file saved successfully.\n");
    return 0;
}

/**
  * For-each loop to save all words.
  */
void for_each_word_save_word(GString *key, struct DIC_ENTRY* dic_entry, GString* contents) {
   
    if ((dic_entry == NULL) || (contents == NULL)) return;
    
    g_string_append_printf(contents, "\n%s", dic_entry->word->str);
    if (dic_entry->flags != NULL) {
        g_string_append_printf(contents, "/");
        g_slist_foreach(dic_entry->flags, (GFunc)for_each_word_flag_save_flag, contents);
    }
    if (dic_entry->optional_fields != NULL) {
        g_string_append_printf(contents, " %s", dic_entry->optional_fields->str);
    }
}

/**
  * For-each loop to save all flags for the word.
  */
void for_each_word_flag_save_flag(struct DIC_FLAG* dic_flag, GString* contents) {
    if ((dic_flag == NULL) || (contents == NULL)) return;
    
    if (g_str_has_suffix(contents->str, "/")) {
        g_string_append_printf(contents, "%d", (int)dic_flag->flag);
    } else {
        g_string_append_printf(contents, ",%d", (int)dic_flag->flag);
    }
}

/**
  * Saves both the dictionary and affix files.
  */
void save() {
    save_affix_file(affix_file);
    save_dictionary_file(dic_file);
}
